在 web 應用中經常需要驗證使用者的權限,例如登入與未登入能看到的頁面可能會不同,需要透過一些方式驗證使用者是否能夠進入該頁面。在 Next.js 中對於 SSG 與 SSR 有不同的驗證方式,以下將會以 next-auth
這個套件為主軸,介紹在 SSG 與 SSR 的頁面中如何做驗證。
在官方文件中還有提到 with-iron-session
這一個套件,先從下載量來看, npm trend 中可以看見 next-auth
的下載量是高於 next-iron-session
的。雖然 next-iron-session
被開源的時間較晚,但如果從 2020 年來看,下載量在當時並沒有差距太多,都落在每週幾千的次的下載量,但是隨著時間推進,到了 2021 年 next-auth
的下載量是高過很多的。
而筆者認為 next-iron-session
的下載量較低的原因是它的寫法可能較不容易被接受,如果使用這個套件,將需要大幅度更動 getServerSideProps
的寫法:
import withSession from '../lib/session'
export const getServerSideProps = withSession(async function ({ req, res }) {
const user = req.session.get('user')
// ...
}
如上面的例子,想要取得 session 的訊息,必須在 function 外用 HOC 的方式包一層 withSession
,才能透過 req.session
取得驗證權限的訊息,這種方法肯定很難讓人被接受。
而 next-auth
的寫法更像似遵照 hook 的思維,可以使用 useSession
或 getSession
取得驗證權限的訊息,而不用破壞原本 Next.js 的程式碼結構。
在 Next.js 由於可以混用 SSR、SSG 與 CSR 三種不同的頁面,所以針對不同的頁面,也會有不同的驗證方式。 SSG 與 CSR 的驗證方式很接近,都是在用戶端驗證,如果驗證失敗則可以使用 next/router
轉址到其他頁面;而 SSR 則是在伺服器端驗證,驗證失敗則可以直接在 getServerSideProps
轉址到其他頁面。
從 web vitals 的角度來看,由於 SSR 會在伺服器端做驗證,所以驗證階段必須越快越好,否則會影響 TTI (Time to Interactive) 與 TTFB (Time to First Byte)。果驗證較慢,不如可以考慮將頁面轉換成 SSG 的形式,有利於提升使用者體驗。
next-auth
是一個提供完整解決方案的套件,包括支援 Google、 Facebook、 Twitter 等第三方登入的方法,也可以使用自定義的驗證方式,支援 OAuth 1.0、1.0A 與 2.0 的驗證,也同時支援 JSON Web Token 與 database session 等的驗證模式,是完整性很高的套件。
next-auth
的 API 與前面提到的 next-iron-session
相較起來更容易閱讀,不僅提供 hook 的 API,而且還有提供在用戶端與伺服器端端都能使用的 getSession
,讓使用者驗證更容易實作。
如果你需要的是用戶端的驗證解決方案, next-auth
可能無法滿足你的需求,因為 next-auth
使用的是伺服器端驗證,在一個頁面中必須使用 next-auth
的 client API - signIn
進行登入驗證,而登入驗證會導入到 next-auth
所屬的 API routes - api/auth/[...nextauth].ts
,然後再根據不同的情況使用第三方登入驗或自定義的驗證。
NextAuth 的官方 GitHub 有 next-auth-example ,如果需要一個模板,可以從跟著 README 玩玩看。
以下我們來看看在 Next.js 中幾個 next-auth
基本的使用方法,包括設定 API routes、在 component 中使用 hook API ,以及使用 context 讓 Next.js 全域都能取得使用者的 session。
next-auth
是一個在伺服器端的驗證套件,首先,我們必須在定義一個 API routes 於 pages/api/auth/[...nextauth].ts
,這個 API routes 與常見的寫法不太一樣,使用的是 next-auth
的 API ,其中包含驗證的 providers
,例如 Google、Facebook、Twitter 等等,如果需要將使用者的資料儲存在資料庫中,可以在 database 這個參數加上 url。
實際上 NextAuth
這個 API 的能夠傳入參數有很多,如果需要知道更多資訊的讀者,可以直接到官方文件中瀏覽。
import NextAuth from "next-auth";
import Providers from "next-auth/providers";
export default NextAuth({
// 使用 Google 驗證
providers: [
Providers.Google({
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
}),
],
// 如果需要將使用者的資料儲存在資料庫中,可以在 database 這個參數加上 url
database: process.env.DATABASE_URL,
});
pages/login/index.ts
NextAuth 有提供 hooks API 讓我們使用,在使用上非常方便,只要從 next-auth/client
引入 useSession
後不用做任何的設定就可以使用。
從下面中的範例中,我們還可以看到 NextAuth 也提供了 signIn
與 signOut
的 function,登入時可以呼叫 signIn
,NextAuth 會幫我們打 API routes ,並經過一連串驗證權限的流程,如果驗證成功,我們就可已透過 useSession
拿到驗證後的資料,例如 accessToken
。
而登出的流程也可以透過 NextAuth 的 signOut
觸發,如此一來,session 中資料就會被清空,使用者便會回到登入前的狀態。
import { signIn, signOut, useSession } from "next-auth/client";
function Login() {
const [session, loading] = useSession();
return (
<>
{!session && <button onClick={() => signIn()}>登入</button>}
{session && (
<>
<p>使用者 email: {session.user.email}</p>
<button onClick={() => signOut()}>登出</button>
</>
)}
</>
);
}
export default Login;
page/_app.ts
由於 NextAuth 是透過 Next.js 的 API routes 驗證使用者,透過上述的 signIn
觸發後, session 會被記錄下來,當我們使用到 Next.js 的 SSR 功能時,會透過 getSession
取得驗證後的一些訊息,例如 accessToken
,我們在一些情況需要在打 API 時帶入 accessToken
才能夠成功獲取資料。
在獲取資料,並且成功由伺服器渲染完頁面後,使用者後續還會跟頁面互動,而在切換頁面時,如果需要不斷地跟伺服器交換使用者是否已認證的訊息,難免會讓一些互動會有些延遲,所以為了解決這個情況,NextAuth 提供了 Context API,讓我們可以在 getServerSideProps
中傳入 session
,如此一來,在頁面切換時,取得使用者的驗證訊息就會更有效率,像是在 accessToken
過期之前,不必再次透過伺服器驗證使用者,而是直接從 Context 取得 accessToken
,藉此提升使用者體驗。
import { AppProps } from "next/app";
import { Provider } from "next-auth/client";
function MyApp({ Component, pageProps }: AppProps) {
return (
<Provider session={pageProps.session}>
<Component {...pageProps} />
</Provider>
);
}
export default MyApp;